#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Practical introduction to Qt in Python
# Copyright (C) 2011-2012  Gonzalo Exequiel Pedone
#
# This Program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This Program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with This Program.  If not, see <http://www.gnu.org/licenses/>.
#
# Email   : hipersayan.x AT gmail.com
# Web-Site: http://hipersayanx.blogger.com/

import os

# Import the "sys" module for passing the command line arguments to Qapplication.
import sys

# "ramdom" module is used for mezclar playlist shuffling.
import random

# The "QtCore" module is the basis for the rest of the Qt modules, it gives access
# to all functionalities of Qt that doesn't requires the initialisation of the
# graphics system.
# The "QtGui" module gives access to all functionalities for creating the graphics
# user interface for our apps, for instance, the widgets, the dialogs, OpenGL
# graphic widgets, etc..
# The "uic" module is exclusive of PyQt. Among other things, this module can
# read and create the graphic interface from a .ui on run time, it also can
# create source code for the GUI from the .ui files.
# .ui files are XML files that defines the attributes and actions of each widget
# into the GUI.
# The phonon module give access to all multimedia functionalities, such as
# music, video, pictures and online streamming play.
from PyQt4 import QtCore, QtGui, uic, phonon

# Import the modules with the dialog of our program.
import addurl
import aboutdlg


# The VideoPlayer class will be a wrapper for MainWindow.
class VideoPlayer(QtGui.QMainWindow):
    def __init__(self):
        # First, we must initialize the constructor of the ancestor class.
        QtGui.QMainWindow.__init__(self)

        # Load the GUI from videoplayer.ui. now, "self" is like as a pointer to
        # MainWindow.
        uic.loadUi('../share/ui/videoplayer.ui', self)

        # A list of phonon.Phonon.MediaSource.
        self.playlist = []

        # This will be a copy of the playlist before shuffling.
        self.playlistTmp = []

        # This flag indicates that when the program reach the end of the
        # playlist, start playing all the playlist again from beginning, or if
        # we are at the beginning of the playlist, go to the last media.
        self.repeat = False

        # Create a menu for adding new medias.
        self.addMediaMenu = QtGui.QMenu()

        # Add a new action to the menu and get a reference to it.
        self.axnAddLocalFile = self.addMediaMenu.addAction(self.tr('Add local '
                                                                   '&File'))

        self.axnAddURL = self.addMediaMenu.addAction(self.tr('Add &URL'))

        # Add the menu to the add media button.
        self.btnAddMedia.setMenu(self.addMediaMenu)

        # When user choose an option from the menu, this action will emit the
        # "triggered" signal, this signal will be connected to his respective
        # slot.
        self.axnAddLocalFile.triggered.\
                             connect(self.on_axnAddLocalFile_triggered)

        self.axnAddURL.triggered.connect(self.on_axnAddURL_triggered)

        # Create a menu for the "About" button.
        self.aboutMenu = QtGui.QMenu()

        # Add the actions to the menu and get a reference to it.
        self.axnAbout = self.aboutMenu.addAction(self.tr('About this &Program'))
        self.axnAboutQt = self.aboutMenu.addAction(self.tr('About &Qt'))

        # Add the menu to the "About" button.
        self.btnAbout.setMenu(self.aboutMenu)

        # Connect the signals.
        self.axnAbout.triggered.connect(self.on_axnAbout_triggered)
        self.axnAboutQt.triggered.connect(self.on_axnAboutQt_triggered)

        # We connect the audio slider to the video player.
        # Doing this we can control the time position of the video or music.
        self.sldVolumeSlider.setAudioOutput(self.vdpVideo.audioOutput())

        # Connect the volume slider to the video player.
        self.sldSeekSlider.setMediaObject(self.vdpVideo.mediaObject())

        # When the current media has finished, it will play the next media in
        # the playlist.
        self.vdpVideo.finished.connect(self.on_btnNext_clicked)

        # Create a timer for update the playtime of the current media and
        # the mouse status.
        self.tmrTimer = QtCore.QTimer(self)

        # The timer will emit the "timeout" signal every a quarter of second.
        self.tmrTimer.setInterval(250)

        # When it reaches the stablished time, the timer will emit the "timeout"
        # signal and it respective slot is called.
        self.tmrTimer.timeout.connect(self.on_tmrTimer_timeout)

        # Starts the timer.
        self.tmrTimer.start()

        # Stores a reference to the position and the current time.
        self.mousePos0 = QtGui.QCursor.pos()
        self.mouseT0 = QtCore.QTime.currentTime()

    # This method updates the playlist when user adds a new media.
    def updatePlayList(self):
        # Create an empty playlist.
        playlist = []

        # For each media to play...
        for source in self.playlist:
            # If the media is a local file...
            if source.type() == phonon.Phonon.MediaSource.LocalFile:
                # Just add the name of the file without the path.
                playlist += [os.path.basename(str(source.fileName()))]
            # If the media is an URL...
            elif source.type() == phonon.Phonon.MediaSource.Url:
                # Add the URL to the list.
                playlist += [str(source.url().toString())]

        # Remove all items in QListWidget
        self.lswPlaylist.clear()

        # add the new playlist.
        self.lswPlaylist.addItems(playlist)

    # mouseDoubleClickEvent is a protected method which called when user does
    # doble click in the GUI and this event isn't catched by anyother widget.
    def mouseDoubleClickEvent(self, event):
        # Always, before process the event, we must send a copy of it to the
        # ancestor class.
        QtGui.QMainWindow.mouseDoubleClickEvent(self, event)

        # Go to fullscreen mode or exit from it.
        self.on_btnFullscreen_clicked()

    # keyPressEvent is a protected method which called when user press a key and
    # this event isn't catched by anyother widget.
    def keyPressEvent(self, event):
        QtGui.QMainWindow.keyPressEvent(self, event)

        key = event.key()

        if key == QtCore.Qt.Key_Space:
            self.on_btnPlayPause_clicked()
            self.btnPlayPause.toggle()
        elif key == QtCore.Qt.Key_Escape:
            self.vdpVideo.stop()
        elif key == QtCore.Qt.Key_PageUp:
            self.on_btnPrevious_clicked()
        elif key == QtCore.Qt.Key_PageDown:
            self.on_btnNext_clicked()
        elif key == QtCore.Qt.Key_E:
            self.on_btnVideoFill_clicked()
            self.btnVideoFill.toggle()
        elif key == QtCore.Qt.Key_F:
            self.on_btnFullscreen_clicked()
        elif key == QtCore.Qt.Key_H:
            self.on_btnAbout_clicked()
        elif key == QtCore.Qt.Key_L:
            self.on_btnShowPlayList_clicked()
            self.btnShowPlayList.toggle()
        elif key == QtCore.Qt.Key_S:
            self.on_btnShufflePlayList_clicked()
            self.btnShufflePlayList.toggle()
        elif key == QtCore.Qt.Key_R:
            self.on_btnRepeatPlayList_clicked()
            self.btnRepeatPlayList.toggle()
        elif key == QtCore.Qt.Key_Delete:
            self.on_btnRemoveMedia_clicked()
        elif key == QtCore.Qt.Key_C:
            self.on_btnClearPlayList_clicked()
        elif key == QtCore.Qt.Key_Left:
            # Backward media 10 seconds.
            self.vdpVideo.seek(self.vdpVideo.currentTime() - 10000)
        elif key == QtCore.Qt.Key_Right:
            # Forward media 10 seconds.
            self.vdpVideo.seek(self.vdpVideo.currentTime() + 10000)
        elif key == QtCore.Qt.Key_Up:
            # Increase the volume.
            volume = self.vdpVideo.volume() + 0.1

            # The volume can be greater than 1, but the sound will be distorted
            # depending of the media.
            if volume > 1:
                volume = 1

            self.vdpVideo.setVolume(volume)
        elif key == QtCore.Qt.Key_Down:
            # Decrease the volume.
            volume = self.vdpVideo.volume() - 0.1

            if volume < 0:
                volume = 0

            self.vdpVideo.setVolume(volume)

    def addMedias(self, medias=[]):
        # Sort the media list alphabetically.
        medias.sort()

        # We assume that we aren't playing any media.
        play = False

        # If user chose at least one media...
        if medias != []:
            # If the playlist is empty, and there aren't a media playing.
            if self.playlist == []:
                play = True

            # Populate the playlist.
            for media in medias:
                if media.startswith('http://') or media.startswith('mms://'):
                    self.playlist += [phonon.Phonon.\
                                             MediaSource(QtCore.QUrl(media))]
                else:
                    self.playlist += [phonon.Phonon.MediaSource(media)]

            self.updatePlayList()

        # If the playlist has been loaded and there aren't a media playing...
        if play and \
           self.vdpVideo.mediaObject().state() != phonon.Phonon.PlayingState:
            # play it.
            self.on_btnPlayPause_clicked()

    @QtCore.pyqtSlot()
    def on_axnAddLocalFile_triggered(self):
        # Open the file selection dialog.
        # The first parameter stablish MainWindow as the parent of the dialog,
        # and the second sets the dialog title.
        filenames = QtGui.QFileDialog.\
                          getOpenFileNames(self, self.tr('Add local files'))

        # Add the filenames to the file names to the playlist.
        self.addMedias(filenames)

    @QtCore.pyqtSlot()
    def on_axnAddURL_triggered(self):
        # Create an instance for the "Add URL" dialog.
        addURLDlg = addurl.AddURL(self)

        # Open the dialog.
        # The program execution will stop here until user close the dialog.
        addURLDlg.exec_()

        if addURLDlg.result() == 0:
            # Add the URLs to the playlist.
            self.addMedias(addURLDlg.urls)

    @QtCore.pyqtSlot()
    def on_axnAbout_triggered(self):
        about = aboutdlg.About(self)
        about.exec_()

    @QtCore.pyqtSlot()
    def on_axnAboutQt_triggered(self):
        QtGui.QMessageBox.aboutQt(self, self.tr('About Qt'))

    @QtCore.pyqtSlot()
    def on_btnClearPlayList_clicked(self):
        # Clear the original and temporal playlist.
        self.playlist = []
        self.playlistTmp = []

        self.updatePlayList()

    @QtCore.pyqtSlot()
    def on_btnFullscreen_clicked(self):
        # If the window is currently in fullscreen mode...
        if self.isFullScreen():
            # show it in normal mode.
            self.showNormal()

            # Show the playlist.
            self.dckPlayList.show()
        else:
            # show it in fullscreen mode.
            self.showFullScreen()

            # Hide the playlist.
            self.dckPlayList.hide()

            # Hide the mouse cursor.
            self.setCursor(QtGui.QCursor(QtCore.Qt.BlankCursor))

    @QtCore.pyqtSlot()
    def on_btnNext_clicked(self):
        if self.playlist == []:
            return

        # Get a reference of the current selected media.
        curIndex = self.lswPlaylist.currentRow()

        # If the current selected media index is less than the size of the
        # playlist...
        if curIndex + 1 < len(self.playlist):
            # increase the index of the current media.
            index = curIndex + 1
        # If the current selected media index is greater than the size of the
        # playlist...
        else:
            # If the repeat mode isn't selected then do nothing.
            if not self.repeat:
                return

            # otherwise, the next media to play will be the first in the list.
            index = 0

        # Select the next media in the playlist
        self.lswPlaylist.setCurrentRow(index,
                                       QtGui.QItemSelectionModel.SelectCurrent)

        # and play it.
        self.on_lswPlaylist_doubleClicked(self.lswPlaylist.item(index))

    @QtCore.pyqtSlot()
    def on_btnPlayPause_clicked(self):
        # If we are playing a media...
        if self.vdpVideo.mediaObject().state() == phonon.Phonon.PlayingState:
            # pause it.
            self.vdpVideo.pause()
        # If the current media is paused...
        elif self.vdpVideo.mediaObject().state() == phonon.Phonon.PausedState:
            # play it.
            self.vdpVideo.play()
        # If there aren't a selected media in the playlist...
        elif self.lswPlaylist.currentRow() < 0:
            # Select the first item in the list.
            self.lswPlaylist.setCurrentRow(0, QtGui.QItemSelectionModel.\
                                                    SelectCurrent)

            # call to the on_lswPlayList_doubleClicked slot to play the media.
            self.on_lswPlaylist_doubleClicked(self.lswPlaylist.currentItem())
        # If there are at least one item selected...
        else:
            # call to the on_lswPlayList_doubleClicked slot to play it.
            self.on_lswPlaylist_doubleClicked(self.lswPlaylist.currentItem())

    @QtCore.pyqtSlot()
    def on_btnPrevious_clicked(self):
        if self.playlist == []:
            return

        # Get a reference of the current selected media.
        curIndex = self.lswPlaylist.currentRow()

        # Sí cur_index - 1 es mayor o igual que el primer ítem de la lista de
        # reproducción...
        # If the current selected media index is greater or equal to the first
        # index of the playlist...
        if curIndex - 1 >= 0:
            # decrease the index of the current media.
            index = curIndex - 1
        # If the current selected media index is less than the first item of the
        # playlist...
        else:
            # If the repeat mode isn't selected then do nothing.
            if not self.repeat:
                return

            # otherwise, the next media to play will be the last in the list.
            index = len(self.playlist) - 1

        # Select the previous media in the playlist
        self.lswPlaylist.setCurrentRow(index,
                                       QtGui.QItemSelectionModel.SelectCurrent)

        # and play it.
        self.on_lswPlaylist_doubleClicked(self.lswPlaylist.item(index))

    @QtCore.pyqtSlot()
    def on_btnRemoveMedia_clicked(self):
        # For each selected media...
        for media in self.lswPlaylist.selectedItems():
            try:
                # get it's index and remove it from the playlist.
                del self.playlist[self.lswPlaylist.row(media)]
            except:
                pass

        self.updatePlayList()

    @QtCore.pyqtSlot()
    def on_btnRepeatPlayList_clicked(self):
        # Toggle on/off repeat mode.
        self.repeat = not self.repeat

    @QtCore.pyqtSlot()
    def on_btnVideoFill_clicked(self):
        # If the btnVideoFill button is checked, the video will be stretched to
        # fill the entire video widget.
        # otherwise, the video will be preserve it's aspect ratio.
        if self.vdpVideo.videoWidget().aspectRatio() == \
           phonon.Phonon.VideoWidget.AspectRatioWidget:
            self.vdpVideo.videoWidget().\
                          setAspectRatio(phonon.Phonon.VideoWidget.\
                                                       AspectRatioAuto)
        else:
            self.vdpVideo.videoWidget().\
                          setAspectRatio(phonon.Phonon.VideoWidget.\
                                                       AspectRatioWidget)

    @QtCore.pyqtSlot()
    def on_btnShowPlayList_clicked(self):
        # Show or hide the playlist.
        self.dckPlayList.setVisible(not self.dckPlayList.isVisible())

    @QtCore.pyqtSlot()
    def on_btnShufflePlayList_clicked(self):
        # If the playlist is empty...
        if self.playlistTmp == []:
            # The temporal playlist will be a copy of the current playlist.
            self.playlistTmp = self.playlist[:]

            # get number of items in the list.
            items = len(self.playlist)

            # Sort the list randomly.
            self.playlist = [self.playlist.pop(
                             random.randint(0, len(self.playlist) - 1))
                             for index in range(items)]
        else:
            # Return the playlist to it's original state.
            self.playlist = self.playlistTmp[:]

            # and clear the temporal playlist.
            self.playlistTmp = []

        self.updatePlayList()

    # When the user do a doble click on an item in QListWidget, it emits the
    # "doubleClicked" signal, and pass a reference to the clicked item as
    # parameter, which is a QtGui.QListWidgetItem type.
    @QtCore.pyqtSlot(QtCore.QModelIndex)
    def on_lswPlaylist_doubleClicked(self, model_index):
        # If the playlist is empty, do nothing.
        if self.playlist == []:
            return

        try:
            # Get the index of model_view, use it to obtain the corresponding
            # MediaSource, and then play it.
            # Here model_index is of the type QtGui.QListWidgetItem.
            self.vdpVideo.play(self.playlist[model_index.row()])
        except:
            # Here model_index is of the type QtGui.QModelIndex.
            self.vdpVideo.play(self.playlist[self.lswPlaylist.row(model_index)])

    @QtCore.pyqtSlot()
    def on_tmrTimer_timeout(self):
        # First update the playtime.

        # Gte the current time mark,
        currentTime = self.vdpVideo.currentTime()

        if currentTime == 0:
            # If the current playtime is 0 seconds, clear the label text.
            self.lblTime.setText('')
        else:
            # Get the total playtime of the media.
            totalTime = self.vdpVideo.totalTime()

            # If the total playtime is less than 1 hour, just show minutes
            # and seconds.
            tFormat = 'mm:ss' if totalTime < 3600000 else 'hh:mm:ss'

            # We use Qtime for time convertions.
            currentTimeH = QtCore.QTime()

            # Convert times to a human readable strings.
            ct = currentTimeH.addMSecs(currentTime).toString(tFormat)

            totalTimeH = QtCore.QTime()
            tt = totalTimeH.addMSecs(totalTime).toString(tFormat)

            # Set time to label.
            self.lblTime.setText(ct + '/' + tt)

        # Now update the mouse status.

        # The window is in fullscreen mode...
        if self.isFullScreen():
            # Update the current mouse time and position.
            mousePos = QtGui.QCursor.pos()
            mouseT = QtCore.QTime.currentTime()

            # Normally, when the program is in fullscreen mode, the mouse must
            # be hidden until user move it.
            if (mousePos != self.mousePos0 and \
                self.cursor().shape() == QtCore.Qt.BlankCursor) or \
                self.wdgVideoControls.isVisible():
                self.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))

                # Reset the time count for calculating the mouse moving time.
                self.mouseT0 = QtCore.QTime.currentTime()
            # If user stops moving the mouse, it must stay visible at least some
            # seconds.
            elif self.cursor().shape() == QtCore.Qt.ArrowCursor and \
                 self.mouseT0.secsTo(mouseT) > 1:
                self.setCursor(QtGui.QCursor(QtCore.Qt.BlankCursor))

            # Update the current mouse position.
            self.mousePos0 = mousePos

            # Convert the global mouse position in the screen to the window
            # local coordinates. And get the coordinate for Y axis.
            mouseY = self.mapFromGlobal(mousePos).y()

            # If the mouse approaches to the position in which must be the
            # controls bar, it must be unhidden.
            if mouseY < self.height() and \
               mouseY > self.height() - self.wdgVideoControls.height():
                if self.wdgVideoControls.isHidden():
                    self.wdgVideoControls.show()
            # otherwise it must stay hidden.
            elif self.wdgVideoControls.isVisible():
                self.wdgVideoControls.hide()
        # The window is in normal mode...
        else:
            # If the mouse cursor is hidden, show it.
            if self.cursor().shape() == QtCore.Qt.BlankCursor:
                self.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))

            # Show playlist.
            if self.wdgVideoControls.isHidden():
                self.wdgVideoControls.show()

# Python does not uses or requires any special main block for executing the
# code. But, under some circumstances could be useful putting the codeto run
# inside of an "if" block that checks the __name__ global variable, if we are
# executing the module alone, __name__ will be equal to '__main__', otherwise,
# if the module is called by other module, __name__ will be equal to te name of
# the calling module.
# This is useful in unitary tests in which we can test each of this modules
# without executing the whole program.
if __name__ == '__main__':
    # Always, before calling a Qt object, you must create an instance of
    # QApplication, which will receive the command line argumments, initialize
    # the working environment, set some global variables, and controls the main
    # loop.
    app = QtGui.QApplication(sys.argv)

    # Load the stylesheet and set it to all children windows.
    with open('../share/styles/style.css') as css:
        app.setStyleSheet(css.read())

    # Creathe an instance of a Qt translator.
    translator = QtCore.QTranslator(app)

    # Get the system session language.
    locale = QtCore.QLocale.system().name()

    # Load the file with the translations.
    translator.load(locale + '.qm', '../share/ts')

    # Set the translator globally.
    app.installTranslator(translator)

    # Set the program name as it will appear in the task manager.
    app.setApplicationName('QtVideoPlayer')

    # Create an instance of VideoPlayer.
    videoPlayer = VideoPlayer()

    # Get a list of the command line argumments.
    arguments = app.arguments()

    # The first argumment is the name of thi script, therefore ignore it, the
    # rest of the argumments will be added as medias.
    videoPlayer.addMedias(arguments[1:])

    # Show the main window of our program.
    videoPlayer.show()

    # Execute the main loop.
    app.exec_()
